#!/usr/bin/env python

"""fsm.py: FSM is a simple finate state machine / engine."""

__author__      = "Adam Lewis"

FSM_ON_ENTER = -1
FSM_ON_EXIT = -2
        
class FSM():
    
    def __init__(self, on_entry_event=FSM_ON_ENTER,
                       on_exit_event=FSM_ON_EXIT,
                       state_table={},
                       initial_state=None):
        """
        Keyword arguments:
            on_entry_event -- Object to pass to event handlers when
                              entering a new state (default -1)
            on_exit_event -- Ojbject to pass to event handlers when
                             exiting the current state (default -2).
            state_table -- Dictionary containing State / Event handler
                           pairs.
            inital_state -- Initial state to use without dispatching
                            the ON_ENTRY event.
                            
        """
                        
        self.states = state_table
        self.current_state = initial_state
        self.on_entry = on_entry_event
        self.on_exit = on_exit_event
        
    def add_state(self, state, handler):
        """Add a new or update an existing state
        
        Keyword arguments:
            state -- Hashable object to represent the state
            handler -- Function to process dispatched events
        
        """
        self.states[state] = handler
        
    def initialize(self, state):
        """Initialize the current state without dispatching an event.
        
        Keyword arguments:
            state -- Hashable object to represent the state
        
        Returns:
            True on success
            False otherwise
        """
        if state in self.states:
            self.current_state = state
            return True
        else:
            return False
        
    def dispatch(self, event):
        """Dispatch the event to the current state.
        
        It is up to the end user to ensure that the event passed in
        is supported by the event handlers.  A simple example of an
        event would be an integer (the default ON_ENTER, ON_EXIT).
        More advanced events could contain additional objects or data,
        for example a tuple of (ID, DATA).  
        
        Keyword arguments:
            event -- Object to pass to the current state
        
        Returns:
            True on success
            False otherwise
        """
        if self.current_state in self.states:
            self.states[self.current_state](event)
            return True
        else:
            return False
        
    def transition(self, new_state):
        """Transition the current state to a new state.
        
        The current state will receive an ON_EXIT event, by default -2,
        before transitioning to the new state.  Once the new state is
        set, it will receive an ON_ENTRY event, by default -1. 
        
        Keyword arguments:
            new_state -- Hashable object to represent the state
        
        Returns:
            True on success
            False otherwise
        """
        # Validate the new state exists
        if new_state not in self.states:
            return False
        
        # Dispatch ON EXIT if the current state is valid
        if self.current_state in self.states:
            self.dispatch(self.on_exit)
             
        self.current_state = new_state
        self.dispatch(self.on_entry)
        return True
        
        
if __name__ == "__main__":
    def start_event_handler(event):
        print "Start", event[0], event[1]
 
    def stop_event_handler(event):
        print "Stop", event[0], event[1]
        
       
    my_fsm = FSM(on_entry_event = (0, "Hello"),
                 on_exit_event = (1, "World"))
                 
    my_fsm.add_state("Start", start_event_handler)
    my_fsm.add_state("Stop", stop_event_handler)
    
    my_fsm.initialize("Start")
    
    my_fsm.dispatch((42, "Random Event 1"))
    my_fsm.dispatch((43, "Random Event 2"))
    my_fsm.transition("Stop")
    my_fsm.dispatch((42, "Random Event 1"))
    my_fsm.dispatch((43, "Random Event 2"))
    
    
    
    
        
    
